/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jpavlich.stemLife;

import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.scene.Spatial;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

/**
 *
 * @author As You Wish
 */
public abstract class LivingAgent extends RigidBodyControl {

    @Gene(500.0)
    public double maxEnergy;
    @Gene(1.0)
    public double idleEnergyConsumption;
    @Display
    protected double energy;
    protected boolean isAlive = true;

    public LivingAgent() {
        super();
        randomizeGenes();
        energy = maxEnergy;

    }

    public Spatial getSpatial() {
        return spatial;
    }

    @Override
    public void update(float tpf) {
        super.update(tpf);
        energy -= idleEnergyConsumption * tpf;
        if (energy < 0) {
            die();
        }
    }

    public void die() {
        isAlive = false;
    }

    public static <T extends LivingAgent> T reproduce(T entity) {

        try {
            Constructor<T> c = (Constructor<T>) entity.getClass().getConstructor(String.class);
            T child = c.newInstance(entity.getClass());

            return child;
        } catch (InstantiationException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SecurityException ex) {
            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public void inheritDna(LivingAgent parent) {
        Class<?> c = getClass();
        if (c.equals(parent.getClass())) {
            do {
                for (Field f : c.getDeclaredFields()) {
                    f.setAccessible(true);

                    Gene g = f.getAnnotation(Gene.class);
                    if (g != null) { // check if attribute is a gene
                        try {
                            if (Math.random() < StemLife.MUTATION_PROBABILITY) {
                                // Mutates gene
                                setGeneValue(f, Math.random() * g.value());
                            } else {
                                // Inherit gene without modifications
                                setGeneValue(f, f.get(parent));
                            }
                        } catch (IllegalArgumentException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IllegalAccessException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                c = c.getSuperclass();
            } while (c != LivingAgent.class);

        }
    }

    private <T> void setGeneValue(Field f, T value) throws IllegalArgumentException, IllegalAccessException {
        if (Settable.class.isAssignableFrom(f.getType())) {
            Settable<T> s = (Settable<T>) f.get(this);
            if (s == null) {
                try {
                    s = (Settable<T>) f.getType().newInstance();
                    f.set(this, s);
                } catch (InstantiationException ex) {
                    Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                    return;
                }
            }
            if (value instanceof Settable) {
                s.set(((Settable<T>) value).get());
            } else {
                s.set(value);
            }
        } else {
            f.set(this, value);
        }
    }

    private void randomizeGenes() {
        Class<?> c = getClass();
        
        if (LivingAgent.class.isAssignableFrom(c)) {
            while (c != RigidBodyControl.class) {
                for (Field f : c.getDeclaredFields()) {
                    f.setAccessible(true);
                    Gene g = f.getAnnotation(Gene.class);
                    if (g != null) { // check if attribute is a gene
                        try {
                            // Assign random value to gene
                            setGeneValue(f, Math.random() * g.value());
                        } catch (IllegalArgumentException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IllegalAccessException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                c = c.getSuperclass();
            } 
        }
    }

    public String dnaToString() {
        StringBuilder sb = new StringBuilder();
        Class<?> c = getClass();
        if (LivingAgent.class.isAssignableFrom(c)) {
            do {
                for (Field f : c.getDeclaredFields()) {
                    f.setAccessible(true);
                    Gene g = f.getAnnotation(Gene.class);
                    if (g != null) { // check if attribute is a gene
                        try {
                            sb.append(f.getName());
                            sb.append(": ");
                            sb.append(f.get(this));
                            sb.append("\n");
                        } catch (IllegalArgumentException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IllegalAccessException ex) {
                            Logger.getLogger(LivingAgent.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                c = c.getSuperclass();
            } while (c != LivingAgent.class);
        }
        return sb.toString();
    }

    
    public String getDescription() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE, true, LivingAgent.class);
    }
}
